package ex2.grammars;

import ex2.Token;
import ex2.grammars.factories.TypeFactory;
import fun.grammar.Word;
import fun.parser.Tree;
import ic.ast.Node;
import ic.ast.decl.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: Roi
 * Date: 11/22/13
 */
public class LibraryGrammar extends AbstractGrammar {

    private final String GRAMMAR =
            "S -> class CLASS_ID { Library_Methods }\n" +
            "Type_Field -> Type | Type_Field [ ]\n" +
            "Type -> int | boolean | string | CLASS_ID\n" +
            "Library_Methods -> Library_Method Library_Methods | Epsilon\n" +
            "Library_Method -> static Ret_Val ID ( Sequence ) ;\n" +
            "Ret_Val -> Type_Field | void\n" +
            "Formal -> Type_Field ID\n" +
            "Sequence -> Formal Commands* | Epsilon\n" +
            "Commands* -> Commands Commands* | Epsilon\n" +
            "Commands -> , Formal\n" +
            "Epsilon ->  \n";

    private List<DeclField> fields = new ArrayList<DeclField>();
    private List<DeclMethod> methods = new ArrayList<DeclMethod>();
    private List<DeclClass> classes = new ArrayList<DeclClass>();

    @Override
    public String getDefinition() {
        return GRAMMAR;
    }

    @Override
    public Node constructAst(Tree parseTree) throws Exception {
        Word root = parseTree.root;
        Tree[] subtrees = parseTree.subtrees.toArray(new Tree[0]);

        switch (root.tag) {
            case "S": {
                if (subtrees.length == 5) {
                    Token token = (Token) subtrees[1].root;
                    constructAst(subtrees[3]);
                    classes.add(new DeclClass(token.getLine(), token.getValue(), fields, methods));
                    return new Program(classes);
                }
            }

            case "void": {
                Token token = (Token) root;
                return new PrimitiveType(token.getLine(), PrimitiveType.DataType.VOID);
            }

            case "Epsilon": {
                return null;
            }

            case "Library_Method": {
                if (subtrees.length == 7) {
                    Token token = (Token) subtrees[2].root;
                    List<Parameter> Formals = new ArrayList<Parameter>();
                    Type Type = (Type) constructAst(subtrees[1]);
                    initFormals(subtrees[4], Formals);
                    return new DeclLibraryMethod(Type, token.getValue(), Formals);
                }
            }

            case "Library_Methods": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    methods.add((DeclLibraryMethod) constructAst(subtrees[0]));
                    constructAst(subtrees[1]);
                    return null;
                }
            }

            case "Ret_Val": {
                if (subtrees.length == 1) {
                    return constructAst(subtrees[0]);
                }
            }

            case "Type": {
                if (subtrees.length == 1) {
                    Token token = (Token) subtrees[0].root;
                    return TypeFactory.create(token);
                }
            }

            case "Type_Field": {
                if (subtrees.length == 1) {
                    return constructAst(subtrees[0]);
                }
                if (subtrees.length == 3) {
                    Type Type = (Type) constructAst(subtrees[0]);
                    for (int i = 1; i <= subtrees.length; i++) {
                        if (i % 2 == 0) {
                            Type.incrementDimension();
                        }
                    }
                    return Type;
                }
            }

            default:
                return null;
        }
    }

    private Node initFormals(Tree parseTree,
                             List<Parameter> Formals) throws Exception {
        Word root = parseTree.root;
        Tree[] subtrees = parseTree.subtrees.toArray(new Tree[0]);

        switch (root.tag) {
            case "Commands": {
                initFormals(subtrees[1], Formals);
                return null;
            }

            case "Commands*": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    initFormals(subtrees[0], Formals);
                    initFormals(subtrees[1], Formals);
                    return null;
                }
            }

            case "Formal": {
                Type Type = (Type) constructAst(subtrees[0]);
                Token token = (Token) subtrees[1].root;
                Formals.add(new Parameter(Type, token.getValue()));
                return null;
            }

            case "Sequence": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    initFormals(subtrees[0], Formals);
                    initFormals(subtrees[1], Formals);
                    return null;
                }
            }

            default:
                return null;
        }
    }
}
